package edu.kufpg.armatus.activity;
import java.util.List;
import java.util.Map;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.TextView;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import edu.kufpg.armatus.R;
import edu.kufpg.armatus.console.ConsoleEntry;
import edu.kufpg.armatus.data.Crumb;
import edu.kufpg.armatus.data.Glyph;
import edu.kufpg.armatus.gesture.OnPinchZoomListener;
import edu.kufpg.armatus.util.BundleUtils;
import edu.kufpg.armatus.util.StringUtils;
public class ConsoleEntryScopeActivity extends ConsoleEntryActivity {
private TextView mTextView;
private ScopeScrollView mScrollView;
private GlyphScopeSpan mSelectedSpan;
private Spannable mSpans;
private Map<GlyphScopeSpan, GlyphScopeSpan> mSpanParentMap = Maps.newHashMap();
private ListMultimap<GlyphScopeSpan, GlyphScopeSpan> mSpanChildrenMap = ArrayListMultimap.create();
private ScaleGestureDetector mScaleGestureDetector;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.console_entry_scope_activity);
mScrollView = (ScopeScrollView) findViewById(R.id.console_entry_scope_scroll_view);
mTextView = (TextView) findViewById(R.id.console_entry_scope_text_view);
final OnPinchZoomListener zoomListener = new OnPinchZoomListener(this, (int) mTextView.getTextSize()) {
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
mTextView.setTextSize(getIntSize());
if (mSelectedSpan != null) {
mTextView.post(new Runnable() {
@Override
public void run() {
mScrollView.clearLines();
selectSpan(mSpans, mSelectedSpan);
mScrollView.invalidate();
}
});
}
super.onScaleEnd(detector);
}
};
mScaleGestureDetector = new ScaleGestureDetector(this, zoomListener);
if (savedInstanceState == null) {
final ConsoleEntry entry = getEntry();
final Spannable spans = StringUtils.charWrap(entry.getCommandResponse().getGlyphText());
final Map<List<Crumb>, GlyphScopeSpan> crumbsSpanMap = Maps.newHashMap();
final int length = spans.length();
int index = 0;
for (final Glyph glyph : entry.getCommandResponse().getGlyphs()) {
int endIndex = index + glyph.getText().length() + 1;
final GlyphScopeSpan span = new GlyphScopeSpan(this, index, Math.min(length, endIndex - 1));
crumbsSpanMap.put(glyph.getPath(), span);
if (glyph.hasBindingSite() && crumbsSpanMap.containsKey(glyph.getBindingSite())) {
GlyphScopeSpan parent = crumbsSpanMap.get(glyph.getBindingSite());
mSpanParentMap.put(span, parent);
mSpanChildrenMap.put(parent, span);
}
spans.setSpan(span, index, Math.min(length, endIndex - 1), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
index = endIndex - 1;
}
mSpans = spans;
} else {
mSpanParentMap = BundleUtils.getMap(savedInstanceState, "spanParentMap");
mSpanChildrenMap = BundleUtils.getListMultimap(savedInstanceState, "spanChildrenMap");
mSelectedSpan = savedInstanceState.getParcelable("selectedSpan");
mSpans = (Spannable) savedInstanceState.getCharSequence("spans");
int textSize = savedInstanceState.getInt("textSize");
for (GlyphScopeSpan span : mSpans.getSpans(0, mSpans.length(), GlyphScopeSpan.class)) {
span.attach(this);
}
if (mSelectedSpan != null) {
mTextView.post(new Runnable() {
@Override
public void run() {
removeHighlight(mSpans);
selectSpan(mSpans, mSelectedSpan);
mTextView.setText(mSpans);
}
});
}
// Watch out! setTextSize() takes sp, but textSize is in pixels!
mTextView.setTextSize(textSize / getResources().getDisplayMetrics().scaledDensity);
zoomListener.setSize(textSize);
}
mTextView.setText(mSpans);
mTextView.setMovementMethod(ScopeMovementMethod.getInstance());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
BundleUtils.putMap(outState, "spanParentMap", mSpanParentMap);
BundleUtils.putMultimap(outState, "spanChildrenMap", mSpanChildrenMap);
outState.putParcelable("selectedSpan", mSelectedSpan);
outState.putCharSequence("spans", mSpans);
outState.putInt("textSize", (int) mTextView.getTextSize());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mScaleGestureDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
private Rect getSpanCoordinates(Spannable buffer, Object span) {
// Initialize global value
Rect parentTextViewRect = new Rect();
// Initialize values for the computing of clickedText position
Layout textViewLayout = mTextView.getLayout();
double startOffsetOfClickedText = buffer.getSpanStart(span);
double endOffsetOfClickedText = buffer.getSpanEnd(span);
double startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int)startOffsetOfClickedText);
double endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int)endOffsetOfClickedText);
// Get the rectangle of the clicked text
int currentLineStartOffset = textViewLayout.getLineForOffset((int)startOffsetOfClickedText);
int currentLineEndOffset = textViewLayout.getLineForOffset((int)endOffsetOfClickedText);
boolean keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset;
textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect);
// Update the rectangle position to his real position on screen
int[] parentTextViewLocation = {0,0};
mTextView.getLocationOnScreen(parentTextViewLocation);
double parentTextViewTopAndBottomOffset = (
parentTextViewLocation[1] -
mTextView.getScrollY() +
mTextView.getCompoundPaddingTop()
);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
// In the case of multi line text, we have to choose what rectangle take
if (keywordIsInMultiLine) {
Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
int screenHeight = size.y;
int dyTop = parentTextViewRect.top;
int dyBottom = screenHeight - parentTextViewRect.bottom;
boolean onTop = dyTop > dyBottom;
if (onTop) {
endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
} else {
parentTextViewRect = new Rect();
textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
}
}
parentTextViewRect.left += (
parentTextViewLocation[0] +
startXCoordinatesOfClickedText +
mTextView.getCompoundPaddingLeft() -
mTextView.getScrollX()
);
parentTextViewRect.right = (int) (
parentTextViewRect.left +
endXCoordinatesOfClickedText -
startXCoordinatesOfClickedText
);
return parentTextViewRect;
}
private void selectSpan(Spannable textViewSpans, GlyphScopeSpan spanToSelect) {
Rect coords = getSpanCoordinates(textViewSpans, spanToSelect);
if (mSpanParentMap.containsKey(spanToSelect)) {
ScopeSpan ps = mSpanParentMap.get(spanToSelect);
mScrollView.drawParentalLine(coords, getSpanCoordinates(textViewSpans, ps));
textViewSpans.setSpan(new HighlightBackgroundSpan(Color.CYAN),
spanToSelect.getStartIndex(), spanToSelect.getEndIndex(), 0);
textViewSpans.setSpan(new HighlightTextSpan(Color.BLACK),
spanToSelect.getStartIndex(), spanToSelect.getEndIndex(), 0);
textViewSpans.setSpan(new HighlightBackgroundSpan(Color.BLUE),
ps.getStartIndex(), ps.getEndIndex(), 0);
} else if (mSpanChildrenMap.containsKey(spanToSelect)) {
List<GlyphScopeSpan> childSpans = mSpanChildrenMap.get(spanToSelect);
Rect[] childRects = new Rect[childSpans.size()];
for (int i = 0; i < childSpans.size(); i++) {
ScopeSpan cs = childSpans.get(i);
childRects[i] = getSpanCoordinates(textViewSpans, cs);
textViewSpans.setSpan(new HighlightBackgroundSpan(Color.CYAN),
cs.getStartIndex(), cs.getEndIndex(), 0);
textViewSpans.setSpan(new HighlightTextSpan(Color.BLACK),
cs.getStartIndex(), cs.getEndIndex(), 0);
}
mScrollView.drawChildLines(coords, childRects);
textViewSpans.setSpan(new HighlightBackgroundSpan(Color.BLUE),
spanToSelect.getStartIndex(), spanToSelect.getEndIndex(), 0);
}
mSelectedSpan = spanToSelect;
}
private void removeHighlight(Spannable spanSource) {
HighlightBackgroundSpan[] hBackSpans = spanSource.getSpans(0, mTextView.length(), HighlightBackgroundSpan.class);
HighlightTextSpan[] hTextSpans = spanSource.getSpans(0, mTextView.length(), HighlightTextSpan.class);
for (HighlightBackgroundSpan s : hBackSpans) {
spanSource.removeSpan(s);
}
for (HighlightTextSpan s : hTextSpans) {
spanSource.removeSpan(s);
}
}
private ScopeScrollView getScrollView() {
return mScrollView;
}
private ScopeSpan getSelectedSpan() {
return mSelectedSpan;
}
private TextView getTextView() {
return mTextView;
}
private void setSelectedSpan(GlyphScopeSpan newSpan) {
mSelectedSpan = newSpan;
}
private static class GlyphScopeSpan extends ScopeSpan {
private ConsoleEntryScopeActivity mActivity;
private GlyphScopeSpan(ConsoleEntryScopeActivity activity, int startIndex, int endIndex) {
super(startIndex, endIndex);
mActivity = activity;
}
@Override
public void onClick(View widget) {
final TextView tv = mActivity.getTextView();
final ScopeScrollView sv = mActivity.getScrollView();
final ScopeSpan selectedSpan = mActivity.getSelectedSpan();
Spannable textViewSpans = new SpannableString(tv.getText());
mActivity.removeHighlight(textViewSpans);
sv.clearLines();
if (selectedSpan == null || !selectedSpan.equals(this)) {
mActivity.selectSpan(textViewSpans, this);
} else if (selectedSpan != null) {
mActivity.setSelectedSpan(null);
}
sv.requestLayout();
tv.setText(textViewSpans);
}
@Override
public void updateDrawState(TextPaint ds) {}
private void attach(ConsoleEntryScopeActivity activity) {
mActivity = activity;
}
}
private static class HighlightBackgroundSpan extends BackgroundColorSpan {
public HighlightBackgroundSpan(int color) {
super(color);
}
public HighlightBackgroundSpan(Parcel src) {
super(src);
}
}
private static class HighlightTextSpan extends ForegroundColorSpan {
public HighlightTextSpan(int color) {
super(color);
}
public HighlightTextSpan(Parcel src) {
super(src);
}
}
}